home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue24 / classtrp / OBJTRAPS.PAS next >
Encoding:
Pascal/Delphi Source File  |  1997-06-02  |  18.6 KB  |  549 lines

  1. //&-& fil: unit ObjTraps (c) 1997 Cyril Jandia /////////////////////////////////
  2. //
  3. //&-& des: &&unit ObjTraps
  4. //&-& use: &&debug &&general
  5. //&-& iou: &&people D.Lantim C.Calvert &&org ETT
  6. //&-& bor: &&D2 &&D3 &&BCB1
  7. //
  8. //&-& cur: 0.3
  9. //&-& cur: (05/24/1997) testing w/ Delphi 3;
  10. //
  11. //&-& aim: unit to trap object creation/destruction
  12. //
  13. //
  14. // Abstract
  15. // --------
  16. // This small quick'n dirty unit allows us to trap object creation/destruction
  17. // through a kind of "black box" we access via quite a simple interface:
  18. // class TClassTrap.
  19. //
  20. // The way we trap object creation/destruction with this class gives us an
  21. // opportunity to have a reference on our objects even when we would say it is
  22. // "too soon" or "too late". In fact, we can really see this class TClassTrap as
  23. // a rather cheap mean to programmatically "hook" the standard execution path of
  24. // Delphi objects' creation/destruction.
  25. // From a timeline point of view, thus we can consider TClassTrap carries it out
  26. // its job at what is the very beginning (ie. 'begin...' line) of a class
  27. // constructor/destructor's body.
  28. // It is said "cheap" because it requires us *no* assembly coding, and what is
  29. // cool too, *nor* source code of any of the classes we want to trap.
  30. //
  31. //
  32. // Examples
  33. // --------
  34. // For instance, one can have an alternative solution to the problem of finding
  35. // the creation order of controls used by foreign forms/units (no source code).
  36. // Another case is how to get the reference to a particular exception object,
  37. // though we are already into the 'finally... end' part of a
  38. // 'try... finally... end' block (reminder: there, Delphi normally ensures that
  39. // ExceptObject = nil - this is the so-called "too late" case above).
  40. // Plus many other cases where, as said before, there is *not* even one line of
  41. // source code for giving us a single little chance to guess...
  42. // "what order on earth this stupid thing (I have carelessly downloaded tonight
  43. // without any doc) is using for its objects' creation/destruction ?!"
  44. //
  45. //
  46. // Usage basics
  47. // ------------
  48. // MakeTraps()
  49. // -----------
  50. // Our central helper function is MakeTraps(), which takes two arguments.
  51. // First, a list of TClasses we want to trap at instance creation/destruction;
  52. // for interface simplicity this list is merely an open 'const array of TClass'.
  53. // Second, what is called a "trap-procedure" of type TTrapProc; see below.
  54. //
  55. // Then, for each TClass that appeared in the first actual parameter of call,
  56. // a new TClassTrap has been built to be internally handled by the unit later.
  57. // The role of TClassTrap objects is to "bind" a TClass value, say 'C', to the
  58. // trap-procedure in order to get the latter called each time an instance of
  59. // class 'C' is created and, later, destroyed.
  60. // A short for this scheme could be - well, has been chosen - "class-trap(s)".
  61. // Note all the class-traps that a call to MakeTraps() has successfully made
  62. // for some TClasses will be effective as soon as MakeTraps() returns.
  63. // The reason for this is TClassTrap installs hooks for a TClass's constructor /
  64. // destructor rather soon itself, since it is done in TClassTrap's own Create.
  65. //
  66. // Problems with MakeTraps()
  67. // -------------------------
  68. // Although MakeTraps() "does its best" to trap the classes listed as the first
  69. // argument, there are cases where all TClassTrap's will not be actually built.
  70. // We can be notified of such cases by examining its result (of type Integer):
  71. // if it is less than the number of elements of the first argument we passed in,
  72. // then there was a problem with some elements of the list.
  73. // Therefore, those classes will *not* be trapped by our scheme.
  74. // Some details about this are in "Note on MakeTraps() calls" below.
  75. //
  76. // TrapCount() and TrapOf()
  77. // ------------------------
  78. // 'TrapCount: Integer'  and  'TrapOf(TClass): TClassTrap' are two helper func-
  79. // tions we can use to know, resp., how many class-traps have been successfully
  80. // built until now and which TClassTrap object traps a particular TClass value
  81. // (or nil if none does so).
  82. //
  83. // Features of TClassTrap
  84. // ----------------------
  85. // TClassTrap has four main properties we will want to use, one of which is
  86. // read/write while others are read-only:
  87. // Trapped: TClass -- the class for which the class-trap has been made - r/o;
  88. // TrapProc: TTrapProc -- the trap-procedure for Trapped class - r/w;
  89. // Count: Integer -- nb of accessible objects of Trapped class - r/o;
  90. // Objects[I: Integer]: TObject -- accessible objects of Trapped class - r/o;
  91. //
  92. // N1***Note on MakeTraps() calls***
  93. // ---------------------------------
  94. // When we call MakeTraps() we have to keep in mind that:
  95. // - naturally enough, nil values are simply ignored along the first argument;
  96. // - a class-trap can be set up for any class, except for TClassTrap itself -
  97. //   this is *not* really an implementation issue: merely a choice
  98. //   made to avoid "tricky" calls to MakeTraps(), anyway useless I guess;
  99. // - although we can call MakeTraps() several times for some TClass value
  100. //   (or pass same TClass value to MakeTraps() several times in one call), there
  101. //   will be only *one* instance of TClassTrap internally bound to the TClass;
  102. // - the previous remark leads us to the following: if we want to change the
  103. //   TrapProc for a trapped TClass, then we have no other choice than having
  104. //   a reference to its trap, and then to use its read/write property TrapProc.
  105. //
  106. // N2***Note to C++Builder users***
  107. // --------------------------------
  108. // We can use C++Builder's "dcc32 -jph[n]" feature to obtain a C++ header file
  109. // from this unit. However, if we do so we have to cope w/ some C++Builder's
  110. // language specifics to use it appropriately:
  111. // - concerning MakeTraps(), we'll find it slightly less easy to call because of
  112. //   C++ open arrays implementation; its prototype being dcc32-generated as:
  113. //
  114. //   extern int __fastcall MakeTraps(System::TMetaClass* const *Classes,
  115. //     const int Classes_Size, TTrapProc ATrapProc);
  116. //
  117. //  ie. where it's a TClass expected in Object Pascal, we have to use the syntax
  118. //   __classid(<type>) in C++ to have the expected 'TMetaClass*' above.
  119. //   For example:
  120. //   if we want to trap ctors/dtors of Memos and Edits on TForm2 launched by
  121. //   TForm1 (eg. TForm2 in 'Available forms' list), we can code something like:
  122. //
  123. //void __fastcall MyTrapProc(TClassTrap*, TObject* &obj, TObjectOperation op)
  124. //{
  125. //  if(op == ooCreate)
  126. //    ::MessageBox(NULL,
  127. //      Format(
  128. //        "%s.Create trapped !",
  129. //        OPENARRAY( // uh! longer than in Object Pascal...
  130. //          TVarRec,
  131. //          (obj->ClassName())
  132. //        )
  133. //      ).c_str(), "", MB_OK
  134. //    );
  135. //  else
  136. //    ::MessageBox(NULL,
  137. //      Format(
  138. //        "%s's %s.Free trapped !",
  139. //        OPENARRAY(
  140. //          TVarRec,
  141. //          (((TComponent*)obj)->Name, obj->ClassName())
  142. //        )
  143. //      ).c_str(), "", MB_OK
  144. //    );
  145. //}
  146. //
  147. //void __fastcall TForm1::FormCreate(TObject *Sender)
  148. //{
  149. //  TMetaClass* cls[2] = { __classid(TMemo), __classid(TEdit) };
  150. //  MakeTraps(EXISTINGARRAY(cls), MyTrapProc);
  151. //}
  152. //
  153. //void __fastcall TForm1::Button1Click(TObject *Sender)
  154. //{
  155. //  Form2 = new TForm2(this);
  156. //  Form2->ShowModal();
  157. //  delete Form2;
  158. //}
  159. //
  160. // - however, it seems there is no problem with such a code either:
  161. //
  162. //void __fastcall TForm1::FormCreate(TObject *Sender)
  163. //{
  164. //  new TClassTrap(__classid(TMemo), MyTrapProc);
  165. //  new TClassTrap(__classid(TEdit), MyTrapProc);
  166. //  // ... & for an extra:
  167. //  new TClassTrap(__classid(TForm1), MyTrapProc); // funny how it works!...
  168. //  new TClassTrap(__classid(TForm2), MyTrapProc);
  169. //}
  170. //
  171. //   which is a bit longer C++ code to write - choose what you find easier.
  172. //
  173. // Note also for misc. that:
  174. // the var parameter Instance in TTrapProc procedure type does permit to have a
  175. // a TrapProc "returning" nil on instance creation. Who will find this useful ?
  176. // ::MessageBox(NULL,...) calls above are used instead of Dialogs::ShowMessage()
  177. // or Dialogs::MessageDlg() so that we are notified even when Form1 dies.
  178. // There is normally no problem calling Free on a trap made by MakeTraps() or to
  179. // call RemoveTrapOf() for a class the trap of which has been made on the fly by
  180. // TClassTrap.Create(); you will see below I tried to design TClassTrap so that
  181. // we can rely either on its "native" methods or on the unit's helper functions
  182. // the way we find more natural.
  183. //
  184. // Now, just have fun...
  185. //
  186. //
  187. // I want to dedicate this work to the only one who can make me the happiest man
  188. // in this world...
  189. // Toi, Caroline Che'rie, Mon Amour
  190. //
  191. // Cyril Jandia aka FLFan - Eiffel(And Delphi)Fan Forever - 01/15/97
  192.  
  193. unit ObjTraps;
  194.  
  195. interface
  196.  
  197. uses
  198.   SysUtils, Classes;
  199.  
  200. type
  201.   TObjectOperation = (ooCreate, ooFree); // todo:comment
  202.  
  203.   TClassTrap = class;
  204.  
  205.   // call-back function type a class-trap uses for every instance creation and
  206.   // destruction a trapped class will carry out
  207.   TTrapProc = procedure(const Trap: TClassTrap; const Instance: TObject;
  208.     Operation: TObjectOperation);
  209.  
  210.   // this simple object allows us to "trap" object creation/destruction by using
  211.   // what is called a "trap-procedure"; see property TrapProc below
  212.   TClassTrap = class
  213.   private
  214.     Unusable: Boolean;
  215.     FHunting: Boolean;
  216.     FTrapped: TClass;
  217.     FObjects: TList{of TObject};
  218.     FTrapProc: TTrapProc;
  219.     FOrgNewInst: Pointer;
  220.     FOrgDestroy: Pointer;
  221.     procedure SetMagicHooks(const Hook1, Hook2: Pointer);
  222.     procedure NotifyOfNewInstance(const Instance: TObject);
  223.     procedure NotifyOfDestruction(const Instance: TObject);
  224.     procedure SetTrapProc(const ATrapProc: TTrapProc);
  225.     function GetCount: Integer;
  226.     function GetObjects(const I: Integer): TObject;
  227.   public
  228.     constructor Create(const AClass: TClass);
  229.     constructor CreateTrap(const AClass: TClass; const ATrapProc: TTrapProc);
  230.     destructor Destroy; override;
  231.     // is the trap effective?
  232.     property Hunting: Boolean read FHunting;
  233.     // class for which the class-trap has been made
  234.     property Trapped: TClass read FTrapped;
  235.     // trap-procedure to call on trapped class' instance creation/destruction
  236.     property TrapProc: TTrapProc read FTrapProc write SetTrapProc;
  237.     // nb of accessible objects of trapped class
  238.     property Count: Integer read GetCount;
  239.     // accessible objects of trapped class
  240.     property Objects[const I: Integer]: TObject read GetObjects; default;
  241.   end;
  242.  
  243. // main function that installs traps for the Classes with the trap-procedure
  244. // pointed to by ATrapProc;
  245. // returns the nb of classes successfully trapped
  246. function MakeTraps(const Classes: array of TClass;
  247.   const ATrapProc: TTrapProc): Integer;
  248.  
  249. // helper function: returns the number of class-traps currently set up
  250. function TrapCount: Integer;
  251.  
  252. // helper function: returns the trap set up for a class, if any, or nil if none
  253. function TrapOf(const AClass: TClass): TClassTrap;
  254.  
  255. // helper procedure: removes the trap that has been set up for a class
  256. procedure RemoveTrapOf(const AClass: TClass);
  257.  
  258. implementation
  259.  
  260. uses
  261.   Windows; // hoho! someone needs Win32 down there (just find who...)
  262.  
  263. type
  264.   PVmt = ^TVmt;
  265.   TVmt = record
  266. {$IFDEF VER100}
  267.     Vmt: Pointer;
  268.     IntfTable: Pointer;
  269. {$ENDIF}
  270.     AutoTable: Pointer;
  271.     InitTable: Pointer;
  272.     TypeInfo: Pointer;
  273.     FieldTable: Pointer;
  274.     MethodTable: Pointer;
  275.     DynMethodTable: Pointer;
  276.     ClassName: PShortString;
  277.     InstanceSize: Cardinal;
  278.     ClassParent: Pointer;
  279. {$IFDEF VER100}
  280.     SafeCallExceptionMethod: Pointer;
  281. {$ENDIF}
  282.     DefaultHandler: Pointer;
  283.     NewInstance: Pointer;
  284.     FreeInstance: Pointer;
  285.     Destroy: Pointer;
  286.   end;
  287.  
  288.   // serves the magic done by TClassTrap.SetMagicHooks()
  289.   TTrappedObject = class
  290.     class function NewInstance: TObject; override;
  291.     destructor Destroy; override;
  292.   end;
  293.  
  294. // the class-traps themselves
  295. var
  296.   Traps: TList{of TClassTrap};
  297.  
  298. function TrapAt(const I: Integer): TClassTrap;
  299. begin
  300.   Result := TClassTrap(Traps[I]);
  301. end;
  302.  
  303. function GetVmt(const AClass: TClass): PVmt;
  304. begin
  305.   Result := PVmt(AClass); if Result <> nil then Dec(Result);
  306. end;
  307.  
  308. function MakeTraps(const Classes: array of TClass;
  309.   const ATrapProc: TTrapProc): Integer;
  310. var
  311.   i: Integer;
  312.   trap: TClassTrap;
  313. begin
  314.   Result := 0;
  315.   for i := 0 to High(Classes) do begin
  316.     trap := TClassTrap.CreateTrap(Classes[i], ATrapProc);
  317.     if not trap.Unusable then Inc(Result);
  318.   end;
  319. end;
  320.  
  321. function TrapCount: Integer;
  322. begin
  323.   Result := Traps.Count;
  324. end;
  325.  
  326. function TrapOf(const AClass: TClass): TClassTrap;
  327. var
  328.   i: Integer;
  329.   trap: TClassTrap;
  330. begin
  331.   Result := nil;
  332.   for i := 0 to TrapCount - 1 do begin
  333.     trap := TrapAt(i);
  334.     if trap.FTrapped = AClass then begin
  335.       Result := trap;
  336.       Break;
  337.     end;
  338.   end;
  339. end;
  340.  
  341. procedure RemoveTrapOf(const AClass: TClass);
  342. var
  343.   trap: TClassTrap;
  344. begin
  345.   trap := TrapOf(AClass);
  346.   if trap <> nil then trap.Free;
  347. end;
  348.  
  349. constructor TClassTrap.Create(const AClass: TClass);
  350. var
  351.   vmt: PVmt;
  352. begin
  353.   if AClass = nil then
  354.     Unusable := True;
  355.   if TrapOf(AClass) <> nil then
  356.     Unusable := True;
  357.   if AClass.InheritsFrom(TClassTrap) then
  358.     Unusable := True;
  359.   vmt := GetVmt(AClass);
  360.   FOrgNewInst := vmt^.NewInstance;
  361.   FOrgDestroy := vmt^.Destroy;
  362.   FTrapped := AClass;
  363.   FObjects := TList.Create;
  364.   FObjects.Capacity := 1024;
  365.   Traps.Add(Self); // take care of updating the unit's trap list
  366. end;
  367.  
  368. constructor TClassTrap.CreateTrap(const AClass: TClass;
  369.   const ATrapProc: TTrapProc);
  370. begin
  371.   Create(AClass);
  372.   SetTrapProc(ATrapProc);
  373. end;
  374.  
  375. destructor TClassTrap.Destroy;
  376. var
  377.   i: Integer;
  378. begin
  379.   // restore original NewInstance and Destroy of Trapped class
  380.   SetMagicHooks(FOrgNewInst, FOrgDestroy);
  381.   i := Traps.IndexOf(Self);
  382.   if i >= 0 then Traps.Delete(i); // take care of updating the unit's trap list
  383.   FObjects.Free;
  384.   inherited Destroy;
  385. end;
  386.  
  387. procedure TClassTrap.NotifyOfNewInstance(const Instance: TObject);
  388. begin
  389.   if Instance <> nil then FObjects.Add(Instance);
  390.   if @FTrapProc <> nil then FTrapProc(Self, Instance, ooCreate);
  391. end;
  392.  
  393. procedure TClassTrap.NotifyOfDestruction(const Instance: TObject);
  394. var
  395.   i: Integer;
  396. begin
  397.   if @FTrapProc <> nil then FTrapProc(Self, Instance, ooFree);
  398.   i := FObjects.IndexOf(Instance);
  399.   if i >= 0 then FObjects.Delete(i);
  400. end;
  401.  
  402. procedure TClassTrap.SetTrapProc(const ATrapProc: TTrapProc);
  403. var
  404.   vmt: PVmt;
  405. begin
  406.   if Unusable then Exit;
  407.   FTrapProc := ATrapProc;
  408.   if @FTrapProc = nil then begin
  409.     SetMagicHooks(FOrgNewInst, FOrgDestroy);
  410.     FHunting := False;
  411.   end
  412.   else begin
  413.     if FHunting then Exit;
  414.     vmt := GetVmt(TTrappedObject);
  415.     SetMagicHooks(vmt^.NewInstance, vmt^.Destroy);
  416.     FHunting := True;
  417.   end;
  418. end;
  419.  
  420. function TClassTrap.GetCount: Integer;
  421. begin
  422.   Result := FObjects.Count;
  423. end;
  424.  
  425. function TClassTrap.GetObjects(const I: Integer): TObject;
  426. begin
  427.   if (I < 0) or (I >= GetCount) then Result := nil
  428.   else Result := FObjects[i];
  429. end;
  430.  
  431. procedure TClassTrap.SetMagicHooks(const Hook1, Hook2: Pointer);
  432. var
  433.   vmt: PVmt;
  434.   prot: Longint;
  435. begin
  436.   // the real thing begins
  437.   vmt := GetVmt(FTrapped);
  438.  
  439.   VirtualProtect(@vmt^.NewInstance,
  440.     SizeOf(Pointer), PAGE_READWRITE, @prot); // let's be brave
  441.   vmt^.NewInstance := Hook1; // let's be yet more brave
  442.  
  443.   // time to be clean: not necessary but easy to do, then...
  444.   VirtualProtect(@vmt^.NewInstance,
  445.     SizeOf(Pointer), prot, @prot);
  446.  
  447.   VirtualProtect(@vmt^.Destroy,
  448.     SizeOf(Pointer), PAGE_READWRITE, @prot);
  449.   vmt^.Destroy := Hook2;
  450.  
  451.   // time to be clean: not necessary but easy to do, then...
  452.   VirtualProtect(@vmt^.Destroy,
  453.     SizeOf(Pointer), prot, @prot);
  454. end;
  455.  
  456. class function TTrappedObject.NewInstance: TObject;
  457. begin
  458.   // following is a really simple line of code, at first look;
  459.   // well, it works fine in most (hey! all?!) cases - "small is beautiful";
  460.   // note, however, it is *not* obvious at all such a code be appropriate, but
  461.   // a careful look at System.pas reveals us this *is* sufficient here to obtain
  462.   // a fresh instance for almost nothing; ok. it needs further testing, still
  463.   Result := inherited NewInstance; // todo:testing
  464.  
  465.   TrapOf(Self).NotifyOfNewInstance(Result); // time to do user job
  466. end;
  467.  
  468. // internal helper function; see below
  469. function OrgDestroyOf(const AClass: TClass): Pointer;
  470. begin
  471.   Result := TrapOf(AClass).FOrgDestroy;
  472. end;
  473.  
  474. destructor TTrappedObject.Destroy;
  475. begin
  476.   // see comment below in asm block(*)
  477.   TrapOf(Self.ClassType).NotifyOfDestruction(Self); // time to do user job
  478.  
  479.   // since compiler-generated code for 'inherited Destroy' is not appropriate
  480.   // here for a necessary call to original Destroy of a trapped class, well,
  481.   // we do have to rely on BASM32 for once:
  482.   asm
  483.     // we got Self.ClassType first(*) to know which TClassTrap, and just below
  484.     // which Trapped TClass are on business
  485.     mov eax, [esi]
  486.     // then get original Destroy of trapped class that we have saved thought-
  487.     // fully during TClassTrap's creation
  488.     call OrgDestroyOf
  489.     // then prepare the call address
  490.     mov ecx, eax
  491.     // to which that will be a call via an instance, not via a TClass
  492.     xor edx, edx
  493.     // by the way, bring back this instance
  494.     mov eax, esi
  495.     // and here we go
  496.     call ecx
  497.   end; // at last: one big thanx to BASM32! ;^) ...Ebony doesn't have it yet |-(
  498. end;
  499.  
  500. // most simple code
  501. initialization
  502.   Traps := TList.Create;
  503.   Traps.Capacity := 1024;
  504. finalization
  505.   while Traps.Count > 0 do TrapAt(0).Free;
  506.   Traps.Free;
  507. end.
  508.  
  509. //&-& ver: revision history
  510. //
  511. //&-& ver: 0.3
  512. //&-& ver: (05/24/1997) testing w/ Delphi 3;
  513. //
  514. //&-& ver: 0.2a
  515. //&-& ver: (01/23/1997) note to C++Builder users added;
  516. //
  517. //&-& ver: 0.2
  518. //&-& ver: (01/16/1997) unit comment header updated;
  519. //&-& ver: (01/16/1997) procedure RemoveTrapOf() added;
  520. //
  521. //&-& ver: 0.1d
  522. //&-& ver: (01/15/1997) unit comment header added for temporary documentation;
  523. //&-& ver: (01/15/1997) + very first try of gentle acknowledgements...
  524. //
  525. //&-& ver: 0.1c
  526. //&-& ver: (01/14/1997) yet another identifiers/comments refresh;
  527. //
  528. //&-& ver: 0.1b
  529. //&-& ver: (01/13/1997) lonesome bug fixed in TClassTrap.NotifyOfDestruction();
  530. //
  531. //&-& ver: 0.1a
  532. //&-& ver: (01/11/1997) revised implementation comments;
  533. //
  534. //&-& ver: 0.1
  535. //&-& ver: (01/11/1997) code made more robust here & there;
  536. //
  537. //&-& ver: 0.0b
  538. //&-& ver: (01/10/1997) implementation comments added;
  539. //
  540. //&-& ver: 0.0a
  541. //&-& ver: (01/09/1997) revised identifiers;
  542. //&-& ver: (01/09/1997) revision history added;
  543. //
  544. //&-& ver: 0.0
  545. //&-& ver: (01/08/1997) initial version:
  546. //&-& ver: (01/08/1997) from experiments made for the "LiveInspector" project.
  547. //
  548. //&-& end: unit ObjTraps (c) 1997 Cyril Jandia /////////////////////////////////
  549.